<html>
<head>
<title>对话剧情.py</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
.s0 { color: #808080;}
.s1 { color: #a9b7c6;}
.s2 { color: #cc7832;}
.s3 { color: #6a8759;}
.s4 { color: #6897bb;}
.s5 { color: #629755; font-style: italic;}
</style>
</head>
<body bgcolor="#2b2b2b">
<table CELLSPACING=0 CELLPADDING=5 COLS=1 WIDTH="100%" BGCOLOR="#606060" >
<tr><td><center>
<font face="Arial, Helvetica" color="#000000">
对话剧情.py</font>
</center></td></tr></table>
<pre><span class="s0"># visual_novel.py</span>
<span class="s2">import </span><span class="s1">tkinter </span><span class="s2">as </span><span class="s1">tk</span>
<span class="s2">from </span><span class="s1">tkinter </span><span class="s2">import </span><span class="s1">messagebox</span>
<span class="s2">from </span><span class="s1">PIL </span><span class="s2">import </span><span class="s1">Image</span><span class="s2">, </span><span class="s1">ImageTk</span>
<span class="s2">from </span><span class="s1">game_core </span><span class="s2">import </span><span class="s1">GameCore</span>
<span class="s2">import </span><span class="s1">os</span>


<span class="s2">class </span><span class="s1">VisualNovelUI:</span>
    <span class="s2">def </span><span class="s1">__init__(self</span><span class="s2">, </span><span class="s1">master</span><span class="s2">, </span><span class="s1">resource_paths):</span>
        <span class="s1">self.master = master</span>
        <span class="s1">master.title(</span><span class="s3">&quot;猎手张小凡 - 完整版&quot;</span><span class="s1">)</span>
        <span class="s1">master.attributes(</span><span class="s3">&quot;-fullscreen&quot;</span><span class="s2">, True</span><span class="s1">)</span>
        <span class="s1">self.screen_width = master.winfo_screenwidth()</span>
        <span class="s1">self.screen_height = master.winfo_screenheight()</span>

        <span class="s1">self.resource_paths = resource_paths</span>
        <span class="s1">self.game_core = GameCore(resource_paths</span><span class="s2">, </span><span class="s1">self.screen_width</span><span class="s2">, </span><span class="s1">self.screen_height)</span>

        <span class="s0"># 窗口状态标志</span>
        <span class="s1">self._window_destroyed = </span><span class="s2">False</span>
        <span class="s1">self.persistent_buttons = []</span>
        <span class="s1">self.current_choice_buttons = []</span>

        <span class="s0"># 显示加载屏幕</span>
        <span class="s1">self.show_loading_screen()</span>
        <span class="s1">master.after(</span><span class="s4">100</span><span class="s2">, </span><span class="s1">self.load_and_init)</span>

    <span class="s2">def </span><span class="s1">load_and_init(self):</span>
        <span class="s5">&quot;&quot;&quot;加载数据并初始化UI&quot;&quot;&quot;</span>
        <span class="s2">try</span><span class="s1">:</span>
            <span class="s1">self.load_initial_data()</span>
        <span class="s2">except </span><span class="s1">Exception </span><span class="s2">as </span><span class="s1">e:</span>
            <span class="s1">messagebox.showerror(</span><span class="s3">&quot;错误&quot;</span><span class="s2">, </span><span class="s3">f&quot;数据加载失败: </span><span class="s2">{</span><span class="s1">str(e)</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>
            <span class="s1">self._destroy_window()</span>
            <span class="s2">return</span>

        <span class="s1">self.init_ui()</span>
        <span class="s1">self.update_chapter_display()</span>

    <span class="s2">def </span><span class="s1">show_loading_screen(self):</span>
        <span class="s5">&quot;&quot;&quot;显示加载屏幕&quot;&quot;&quot;</span>
        <span class="s1">self.bg_canvas = tk.Canvas(self.master</span><span class="s2">, </span><span class="s1">width=self.screen_width</span><span class="s2">, </span><span class="s1">height=self.screen_height)</span>
        <span class="s1">self.bg_canvas.pack()</span>
        <span class="s1">self.bg_canvas.create_text(</span>
            <span class="s1">self.screen_width / </span><span class="s4">2</span><span class="s2">, </span><span class="s1">self.screen_height / </span><span class="s4">2</span><span class="s2">,</span>
            <span class="s1">text=</span><span class="s3">&quot;加载中...&quot;</span><span class="s2">,</span>
            <span class="s1">font=(</span><span class="s3">&quot;微软雅黑&quot;</span><span class="s2">, </span><span class="s4">36</span><span class="s1">)</span><span class="s2">,</span>
            <span class="s1">fill=</span><span class="s3">&quot;white&quot;</span>
        <span class="s1">)</span>

    <span class="s2">def </span><span class="s1">_destroy_window(self):</span>
        <span class="s5">&quot;&quot;&quot;安全销毁窗口的方法&quot;&quot;&quot;</span>
        <span class="s2">if not </span><span class="s1">self._window_destroyed </span><span class="s2">and </span><span class="s1">self.master.winfo_exists():</span>
            <span class="s1">self._window_destroyed = </span><span class="s2">True</span>
            <span class="s1">self.master.destroy()</span>

    <span class="s2">def </span><span class="s1">load_initial_data(self):</span>
        <span class="s5">&quot;&quot;&quot;加载初始游戏数据&quot;&quot;&quot;</span>
        <span class="s1">self.game_core.load_game_data()</span>
        <span class="s1">self.game_core.preload_resources()</span>

    <span class="s2">def </span><span class="s1">init_ui(self):</span>
        <span class="s5">&quot;&quot;&quot;初始化用户界面&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s0"># 清除加载屏幕</span>
        <span class="s1">self.bg_canvas.destroy()</span>

        <span class="s1">self.bg_canvas = tk.Canvas(self.master</span><span class="s2">, </span><span class="s1">width=self.screen_width</span><span class="s2">, </span><span class="s1">height=self.screen_height)</span>
        <span class="s1">self.bg_canvas.pack()</span>

        <span class="s0"># 创建角色立绘框架</span>
        <span class="s1">self.char_frames = {}</span>
        <span class="s2">for </span><span class="s1">char_id </span><span class="s2">in </span><span class="s1">[</span><span class="s3">&quot;1&quot;</span><span class="s2">, </span><span class="s3">&quot;2&quot;</span><span class="s1">]:  </span><span class="s0"># 只需要两个角色框架</span>
            <span class="s1">frame = tk.Label(self.master</span><span class="s2">, </span><span class="s1">bd=</span><span class="s4">0</span><span class="s1">)</span>
            <span class="s1">frame.place(relx=</span><span class="s4">0.5</span><span class="s2">, </span><span class="s1">rely=</span><span class="s4">0.6</span><span class="s2">, </span><span class="s1">anchor=</span><span class="s3">&quot;center&quot;</span><span class="s1">)</span>
            <span class="s1">self.char_frames[char_id] = frame</span>

        <span class="s1">self.setup_dialogue_box()</span>
        <span class="s1">self.bind_events()</span>

    <span class="s2">def </span><span class="s1">setup_dialogue_box(self):</span>
        <span class="s5">&quot;&quot;&quot;创建对话框&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">self.dialogue_canvas = tk.Canvas(self.master</span><span class="s2">,</span>
                                         <span class="s1">width=</span><span class="s4">0.9 </span><span class="s1">* self.screen_width</span><span class="s2">,</span>
                                         <span class="s1">height=</span><span class="s4">0.2 </span><span class="s1">* self.screen_height)</span>
        <span class="s1">self.dialogue_canvas.place(relx=</span><span class="s4">0.5</span><span class="s2">, </span><span class="s1">rely=</span><span class="s4">0.85</span><span class="s2">, </span><span class="s1">anchor=</span><span class="s3">&quot;center&quot;</span><span class="s1">)</span>

        <span class="s0"># 创建对话框背景</span>
        <span class="s2">if </span><span class="s1">self.game_core.ui_background_loaded:</span>
            <span class="s1">self.dialogue_bg = self.dialogue_canvas.create_image(</span>
                <span class="s4">0</span><span class="s2">, </span><span class="s4">0</span><span class="s2">,</span>
                <span class="s1">image=self.game_core.ui_backgrounds[</span><span class="s3">&quot;dialogue_box&quot;</span><span class="s1">]</span><span class="s2">,</span>
                <span class="s1">anchor=</span><span class="s3">&quot;nw&quot;</span>
            <span class="s1">)</span>
        <span class="s2">else</span><span class="s1">:</span>
            <span class="s1">self.dialogue_bg = self.dialogue_canvas.create_rectangle(</span>
                <span class="s4">0</span><span class="s2">, </span><span class="s4">0</span><span class="s2">, </span><span class="s4">0.9 </span><span class="s1">* self.screen_width</span><span class="s2">, </span><span class="s4">0.2 </span><span class="s1">* self.screen_height</span><span class="s2">,</span>
                <span class="s1">fill=</span><span class="s3">&quot;#1a1a1a&quot;</span><span class="s2">, </span><span class="s1">outline=</span><span class="s3">&quot;&quot;</span>
            <span class="s1">)</span>

        <span class="s1">self.text_item = self.dialogue_canvas.create_text(</span>
            <span class="s4">40</span><span class="s2">, </span><span class="s4">53</span><span class="s2">, </span><span class="s1">font=(</span><span class="s3">&quot;微软雅黑&quot;</span><span class="s2">, </span><span class="s1">int(self.screen_height * </span><span class="s4">0.025</span><span class="s1">))</span><span class="s2">,</span>
            <span class="s1">fill=</span><span class="s3">&quot;#FF3333&quot;</span><span class="s2">, </span><span class="s1">width=</span><span class="s4">0.85 </span><span class="s1">* self.screen_width</span><span class="s2">, </span><span class="s1">anchor=</span><span class="s3">&quot;nw&quot;</span>
        <span class="s1">)</span>
        <span class="s1">self.name_label = tk.Label(self.dialogue_canvas</span><span class="s2">,</span>
                                   <span class="s1">font=(</span><span class="s3">&quot;微软雅黑&quot;</span><span class="s2">, </span><span class="s1">int(self.screen_height * </span><span class="s4">0.03</span><span class="s1">))</span><span class="s2">,</span>
                                   <span class="s1">bg=</span><span class="s3">&quot;#000000&quot;</span><span class="s2">, </span><span class="s1">fg=</span><span class="s3">&quot;#FF3333&quot;</span><span class="s1">)</span>
        <span class="s1">self.name_label.place(x=</span><span class="s4">40</span><span class="s2">, </span><span class="s1">y=</span><span class="s4">10</span><span class="s1">)</span>

    <span class="s2">def </span><span class="s1">bind_events(self):</span>
        <span class="s5">&quot;&quot;&quot;绑定交互事件&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">self.dialogue_canvas.bind(</span><span class="s3">&quot;&lt;Button-1&gt;&quot;</span><span class="s2">, </span><span class="s1">self.next_dialogue)</span>
        <span class="s1">self.master.bind(</span><span class="s3">&quot;&lt;Escape&gt;&quot;</span><span class="s2">, lambda </span><span class="s1">e: self._destroy_window())</span>

    <span class="s2">def </span><span class="s1">update_chapter_display(self):</span>
        <span class="s5">&quot;&quot;&quot;更新章节显示&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s2">if </span><span class="s1">self.game_core.current_chapter &gt;= len(self.game_core.game_data[</span><span class="s3">&quot;chapters&quot;</span><span class="s1">]):</span>
            <span class="s1">self.show_ending()</span>
            <span class="s2">return</span>

        <span class="s1">chapter = self.game_core.game_data[</span><span class="s3">&quot;chapters&quot;</span><span class="s1">][self.game_core.current_chapter]</span>
        <span class="s1">print(</span><span class="s3">f&quot;加载章节: </span><span class="s2">{</span><span class="s1">chapter.get(</span><span class="s3">'title'</span><span class="s2">, </span><span class="s3">'未命名章节'</span><span class="s1">)</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>

        <span class="s1">self.show_background(chapter[</span><span class="s3">&quot;background&quot;</span><span class="s1">])</span>
        <span class="s1">self.game_core.play_bgm(chapter.get(</span><span class="s3">&quot;music&quot;</span><span class="s1">))</span>
        <span class="s1">self.current_dialogue_idx = </span><span class="s4">0</span>
        <span class="s1">self.show_current_dialogue()</span>

    <span class="s2">def </span><span class="s1">show_background(self</span><span class="s2">, </span><span class="s1">filename):</span>
        <span class="s5">&quot;&quot;&quot;显示背景图&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">self.bg_canvas.delete(</span><span class="s3">&quot;all&quot;</span><span class="s1">)</span>
        <span class="s1">self.bg_canvas.create_image(</span><span class="s4">0</span><span class="s2">, </span><span class="s4">0</span><span class="s2">,</span>
                                    <span class="s1">image=self.game_core.backgrounds[filename]</span><span class="s2">,</span>
                                    <span class="s1">anchor=</span><span class="s3">&quot;nw&quot;</span><span class="s1">)</span>

    <span class="s2">def </span><span class="s1">show_current_dialogue(self):</span>
        <span class="s5">&quot;&quot;&quot;显示当前对话&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">chapter = self.game_core.game_data[</span><span class="s3">&quot;chapters&quot;</span><span class="s1">][self.game_core.current_chapter]</span>

        <span class="s2">if </span><span class="s1">self.current_dialogue_idx &gt;= len(chapter[</span><span class="s3">&quot;dialogues&quot;</span><span class="s1">]):</span>
            <span class="s0"># 检查章节是否有选择项</span>
            <span class="s2">if </span><span class="s3">&quot;choices&quot; </span><span class="s2">in </span><span class="s1">chapter:</span>
                <span class="s1">self.show_choices(chapter[</span><span class="s3">&quot;choices&quot;</span><span class="s1">])</span>
            <span class="s2">else</span><span class="s1">:</span>
                <span class="s0"># 没有更多对话，进入下一章节</span>
                <span class="s1">self.game_core.current_chapter += </span><span class="s4">1</span>
                <span class="s1">self.update_chapter_display()</span>
            <span class="s2">return</span>

        <span class="s1">dialogue = chapter[</span><span class="s3">&quot;dialogues&quot;</span><span class="s1">][self.current_dialogue_idx]</span>
        <span class="s1">self.show_dialogue(dialogue)</span>

    <span class="s2">def </span><span class="s1">show_dialogue(self</span><span class="s2">, </span><span class="s1">dialogue):</span>
        <span class="s5">&quot;&quot;&quot;显示对话内容&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s0"># 确保active是字符串类型</span>
        <span class="s1">active_char = str(dialogue[</span><span class="s3">&quot;active&quot;</span><span class="s1">])</span>

        <span class="s0"># 隐藏所有角色</span>
        <span class="s2">for </span><span class="s1">char_id </span><span class="s2">in </span><span class="s1">self.char_frames:</span>
            <span class="s1">self.char_frames[char_id].config(image=</span><span class="s2">None</span><span class="s1">)</span>

        <span class="s0"># 确保角色立绘已加载</span>
        <span class="s2">if </span><span class="s1">active_char </span><span class="s2">not in </span><span class="s1">self.game_core.characters:</span>
            <span class="s1">active_char = </span><span class="s3">&quot;2&quot;  </span><span class="s0"># 默认使用角色2</span>
            <span class="s2">if </span><span class="s1">active_char </span><span class="s2">not in </span><span class="s1">self.game_core.characters:</span>
                <span class="s1">print(</span><span class="s3">&quot;错误: 角色2未定义&quot;</span><span class="s1">)</span>
                <span class="s2">return</span>

        <span class="s0"># 显示当前角色</span>
        <span class="s2">if </span><span class="s3">&quot;sprite&quot; </span><span class="s2">in </span><span class="s1">dialogue:</span>
            <span class="s1">sprite_name = dialogue[</span><span class="s3">&quot;sprite&quot;</span><span class="s1">]</span>
            <span class="s2">if </span><span class="s1">active_char </span><span class="s2">not in </span><span class="s1">self.game_core.character_sprites </span><span class="s2">or </span><span class="s1">\</span>
                    <span class="s1">sprite_name </span><span class="s2">not in </span><span class="s1">self.game_core.character_sprites[active_char]:</span>
                <span class="s1">print(</span><span class="s3">f&quot;加载特殊立绘: </span><span class="s2">{</span><span class="s1">sprite_name</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>
                <span class="s1">self.game_core.load_character(active_char</span><span class="s2">, </span><span class="s1">sprite_name</span><span class="s2">, </span><span class="s1">is_sprite=</span><span class="s2">True</span><span class="s1">)</span>
            <span class="s1">img = self.game_core.character_sprites[active_char][sprite_name]</span>
        <span class="s2">else</span><span class="s1">:</span>
            <span class="s1">img = self.game_core.characters[active_char][</span><span class="s3">&quot;normal&quot;</span><span class="s1">]</span>

        <span class="s0"># 调整立绘位置</span>
        <span class="s1">relx = </span><span class="s4">0.05 </span><span class="s2">if </span><span class="s1">active_char == </span><span class="s3">&quot;1&quot; </span><span class="s2">else </span><span class="s4">0.95</span>
        <span class="s1">anchor = </span><span class="s3">&quot;w&quot; </span><span class="s2">if </span><span class="s1">active_char == </span><span class="s3">&quot;1&quot; </span><span class="s2">else </span><span class="s3">&quot;e&quot;</span>

        <span class="s1">self.char_frames[active_char].place(relx=relx</span><span class="s2">, </span><span class="s1">rely=</span><span class="s4">0.6</span><span class="s2">, </span><span class="s1">anchor=anchor)</span>
        <span class="s1">self.char_frames[active_char].config(image=img)</span>
        <span class="s1">self.char_frames[active_char].image = img  </span><span class="s0"># 防止被垃圾回收</span>

        <span class="s0"># 更新对话框</span>
        <span class="s1">char_data = self.game_core.game_data[</span><span class="s3">&quot;characters&quot;</span><span class="s1">].get(</span>
            <span class="s1">active_char</span><span class="s2">, </span><span class="s1">{</span><span class="s3">&quot;name&quot;</span><span class="s1">: </span><span class="s3">&quot;未知角色&quot;</span><span class="s2">, </span><span class="s3">&quot;color&quot;</span><span class="s1">: </span><span class="s3">&quot;#FFFFFF&quot;</span><span class="s1">}</span>
        <span class="s1">)</span>
        <span class="s1">self.name_label.config(text=char_data[</span><span class="s3">&quot;name&quot;</span><span class="s1">]</span><span class="s2">, </span><span class="s1">fg=char_data[</span><span class="s3">&quot;color&quot;</span><span class="s1">])</span>
        <span class="s1">self.dialogue_canvas.itemconfig(self.text_item</span><span class="s2">, </span><span class="s1">text=dialogue[</span><span class="s3">&quot;text&quot;</span><span class="s1">])</span>

        <span class="s0"># 处理背景切换</span>
        <span class="s2">if </span><span class="s3">&quot;background&quot; </span><span class="s2">in </span><span class="s1">dialogue:</span>
            <span class="s1">self.show_background(dialogue[</span><span class="s3">&quot;background&quot;</span><span class="s1">])</span>

        <span class="s0"># 播放音效和特效</span>
        <span class="s2">if </span><span class="s3">&quot;sound&quot; </span><span class="s2">in </span><span class="s1">dialogue:</span>
            <span class="s1">self.game_core.play_sound(dialogue[</span><span class="s3">&quot;sound&quot;</span><span class="s1">])</span>
        <span class="s2">if </span><span class="s3">&quot;effect&quot; </span><span class="s2">in </span><span class="s1">dialogue:</span>
            <span class="s1">self.handle_effect(dialogue[</span><span class="s3">&quot;effect&quot;</span><span class="s1">])</span>

        <span class="s0"># 检查是否有选择项</span>
        <span class="s2">if </span><span class="s3">&quot;choices&quot; </span><span class="s2">in </span><span class="s1">dialogue:</span>
            <span class="s1">self.master.after(</span><span class="s4">100</span><span class="s2">, lambda</span><span class="s1">: self.show_choices(dialogue[</span><span class="s3">&quot;choices&quot;</span><span class="s1">]))</span>

    <span class="s2">def </span><span class="s1">next_dialogue(self</span><span class="s2">, </span><span class="s1">event=</span><span class="s2">None</span><span class="s1">):</span>
        <span class="s5">&quot;&quot;&quot;显示下一个对话&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">self.current_dialogue_idx += </span><span class="s4">1</span>
        <span class="s1">self.show_current_dialogue()</span>

    <span class="s2">def </span><span class="s1">handle_effect(self</span><span class="s2">, </span><span class="s1">effect):</span>
        <span class="s5">&quot;&quot;&quot;处理特效&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s2">if </span><span class="s1">effect == </span><span class="s3">&quot;画面震动&quot;</span><span class="s1">:</span>
            <span class="s1">self.shake_screen(</span><span class="s4">5</span><span class="s2">, </span><span class="s4">200</span><span class="s1">)</span>
        <span class="s2">elif </span><span class="s1">effect == </span><span class="s3">&quot;特写镜头&quot;</span><span class="s1">:</span>
            <span class="s1">self.zoom_character()</span>

    <span class="s2">def </span><span class="s1">shake_screen(self</span><span class="s2">, </span><span class="s1">amplitude</span><span class="s2">, </span><span class="s1">duration):</span>
        <span class="s5">&quot;&quot;&quot;屏幕震动效果&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">original_x = self.master.winfo_x()</span>
        <span class="s1">original_y = self.master.winfo_y()</span>

        <span class="s2">for </span><span class="s1">i </span><span class="s2">in </span><span class="s1">range(</span><span class="s4">0</span><span class="s2">, </span><span class="s1">duration</span><span class="s2">, </span><span class="s4">50</span><span class="s1">):</span>
            <span class="s1">offset_x = amplitude * (-</span><span class="s4">1</span><span class="s1">) ** int(i / </span><span class="s4">50</span><span class="s1">)</span>
            <span class="s1">offset_y = amplitude * (-</span><span class="s4">1</span><span class="s1">) ** (int(i / </span><span class="s4">50</span><span class="s1">) + </span><span class="s4">1</span><span class="s1">)</span>
            <span class="s1">self.master.geometry(</span><span class="s3">f&quot;+</span><span class="s2">{</span><span class="s1">original_x + offset_x</span><span class="s2">}</span><span class="s3">+</span><span class="s2">{</span><span class="s1">original_y + offset_y</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>
            <span class="s1">self.master.update()</span>
            <span class="s1">self.master.after(</span><span class="s4">50</span><span class="s1">)</span>

        <span class="s1">self.master.geometry(</span><span class="s3">f&quot;+</span><span class="s2">{</span><span class="s1">original_x</span><span class="s2">}</span><span class="s3">+</span><span class="s2">{</span><span class="s1">original_y</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>

    <span class="s2">def </span><span class="s1">zoom_character(self):</span>
        <span class="s5">&quot;&quot;&quot;角色特写效果&quot;&quot;&quot;</span>
        <span class="s0"># 实现角色放大效果</span>
        <span class="s2">pass</span>

    <span class="s2">def </span><span class="s1">show_choices(self</span><span class="s2">, </span><span class="s1">choices):</span>
        <span class="s5">&quot;&quot;&quot;显示选择项&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s0"># 清除现有选择按钮</span>
        <span class="s2">for </span><span class="s1">widget </span><span class="s2">in </span><span class="s1">self.master.winfo_children():</span>
            <span class="s2">if </span><span class="s1">isinstance(widget</span><span class="s2">, </span><span class="s1">tk.Button) </span><span class="s2">and </span><span class="s1">widget </span><span class="s2">not in </span><span class="s1">self.persistent_buttons:</span>
                <span class="s1">widget.destroy()</span>

        <span class="s0"># 创建选择按钮</span>
        <span class="s1">y_start = (self.screen_height - len(choices) * </span><span class="s4">140</span><span class="s1">) // </span><span class="s4">2</span>

        <span class="s0"># 自定义按钮样式</span>
        <span class="s1">button_style = {</span>
            <span class="s3">&quot;font&quot;</span><span class="s1">: (</span><span class="s3">&quot;微软雅黑&quot;</span><span class="s2">, </span><span class="s4">20</span><span class="s1">)</span><span class="s2">,</span>
            <span class="s3">&quot;bg&quot;</span><span class="s1">: </span><span class="s3">&quot;#1a1a1a&quot;</span><span class="s2">,</span>
            <span class="s3">&quot;fg&quot;</span><span class="s1">: </span><span class="s3">&quot;#ffffff&quot;</span><span class="s2">,</span>
            <span class="s3">&quot;bd&quot;</span><span class="s1">: </span><span class="s4">0</span><span class="s2">,</span>
            <span class="s3">&quot;padx&quot;</span><span class="s1">: </span><span class="s4">30</span><span class="s2">,  </span><span class="s0"># 增加左右内边距</span>
            <span class="s3">&quot;pady&quot;</span><span class="s1">: </span><span class="s4">15</span><span class="s2">,  </span><span class="s0"># 调整上下内边距</span>
            <span class="s3">&quot;relief&quot;</span><span class="s1">: tk.FLAT</span><span class="s2">,</span>
            <span class="s3">&quot;wraplength&quot;</span><span class="s1">: int(self.screen_width * </span><span class="s4">0.4</span><span class="s1">)</span><span class="s2">,  </span><span class="s0"># 设定最大换行宽度（根据屏幕宽度动态计算）</span>
            <span class="s0"># 移除固定width/height，使用自适应大小</span>
        <span class="s1">}</span>

        <span class="s0"># 如果有UI背景，使用背景图片</span>
        <span class="s2">if </span><span class="s1">self.game_core.ui_background_loaded:</span>
            <span class="s1">button_style[</span><span class="s3">&quot;image&quot;</span><span class="s1">] = self.game_core.ui_backgrounds[</span><span class="s3">&quot;button_normal&quot;</span><span class="s1">]</span>
            <span class="s1">button_style[</span><span class="s3">&quot;compound&quot;</span><span class="s1">] = tk.CENTER</span>

        <span class="s0"># 存储创建的按钮</span>
        <span class="s1">self.current_choice_buttons = []</span>

        <span class="s2">for </span><span class="s1">i</span><span class="s2">, </span><span class="s1">choice </span><span class="s2">in </span><span class="s1">enumerate(choices):</span>
            <span class="s1">btn = tk.Button(self.master</span><span class="s2">, </span><span class="s1">text=choice[</span><span class="s3">&quot;text&quot;</span><span class="s1">]</span><span class="s2">,</span>
                            <span class="s1">command=</span><span class="s2">lambda </span><span class="s1">n=choice[</span><span class="s3">&quot;next_chapter&quot;</span><span class="s1">]: self.select_choice(n)</span><span class="s2">,</span>
                            <span class="s1">**button_style)</span>

            <span class="s0"># 添加悬停效果</span>
            <span class="s2">if </span><span class="s1">self.game_core.ui_background_loaded:</span>
                <span class="s1">btn.bind(</span><span class="s3">&quot;&lt;Enter&gt;&quot;</span><span class="s2">, lambda </span><span class="s1">e</span><span class="s2">, </span><span class="s1">b=btn: b.config(</span>
                    <span class="s1">image=self.game_core.ui_backgrounds[</span><span class="s3">&quot;button_hover&quot;</span><span class="s1">]</span><span class="s2">,</span>
                    <span class="s1">bg=</span><span class="s3">&quot;#333333&quot;</span><span class="s2">,</span>
                    <span class="s1">fg=</span><span class="s3">&quot;#ffffff&quot;</span>
                <span class="s1">))</span>
                <span class="s1">btn.bind(</span><span class="s3">&quot;&lt;Leave&gt;&quot;</span><span class="s2">, lambda </span><span class="s1">e</span><span class="s2">, </span><span class="s1">b=btn: b.config(</span>
                    <span class="s1">image=self.game_core.ui_backgrounds[</span><span class="s3">&quot;button_normal&quot;</span><span class="s1">]</span><span class="s2">,</span>
                    <span class="s1">bg=</span><span class="s3">&quot;#1a1a1a&quot;</span><span class="s2">,</span>
                    <span class="s1">fg=</span><span class="s3">&quot;#ffffff&quot;</span>
                <span class="s1">))</span>

            <span class="s0"># 计算按钮高度（根据行数自动调整，可选优化）</span>
            <span class="s1">text_lines = choice[</span><span class="s3">&quot;text&quot;</span><span class="s1">].count(</span><span class="s3">&quot;</span><span class="s2">\n</span><span class="s3">&quot;</span><span class="s1">) + </span><span class="s4">1  </span><span class="s0"># 支持手动换行</span>
            <span class="s1">btn_height = </span><span class="s4">40 </span><span class="s1">+ </span><span class="s4">15 </span><span class="s1">* (text_lines - </span><span class="s4">1</span><span class="s1">)  </span><span class="s0"># 基础高度+每行增量</span>
            <span class="s1">btn.config(height=text_lines)  </span><span class="s0"># 告知按钮行数（需配合pady调整）</span>

            <span class="s1">btn.place(x=self.screen_width // </span><span class="s4">2</span><span class="s2">, </span><span class="s1">y=y_start + i * (btn_height + </span><span class="s4">20</span><span class="s1">)</span><span class="s2">, </span><span class="s1">anchor=</span><span class="s3">&quot;center&quot;</span><span class="s1">)  </span><span class="s0"># 增加按钮间距</span>
            <span class="s1">btn.image = button_style.get(</span><span class="s3">&quot;image&quot;</span><span class="s1">)</span>
            <span class="s1">self.current_choice_buttons.append(btn)</span>

    <span class="s2">def </span><span class="s1">select_choice(self</span><span class="s2">, </span><span class="s1">next_chapter):</span>
        <span class="s5">&quot;&quot;&quot;处理选择&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">print(</span><span class="s3">f&quot;选择分支: </span><span class="s2">{</span><span class="s1">next_chapter</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>

        <span class="s0"># 清除选择按钮</span>
        <span class="s2">for </span><span class="s1">btn </span><span class="s2">in </span><span class="s1">getattr(self</span><span class="s2">, </span><span class="s3">'current_choice_buttons'</span><span class="s2">, </span><span class="s1">[]):</span>
            <span class="s2">if </span><span class="s1">btn.winfo_exists():</span>
                <span class="s1">btn.destroy()</span>

        <span class="s2">try</span><span class="s1">:</span>
            <span class="s0"># 清除当前UI</span>
            <span class="s1">self.bg_canvas.delete(</span><span class="s3">&quot;all&quot;</span><span class="s1">)</span>
            <span class="s2">for </span><span class="s1">char_id </span><span class="s2">in </span><span class="s1">self.char_frames:</span>
                <span class="s1">self.char_frames[char_id].config(image=</span><span class="s2">None</span><span class="s1">)</span>

            <span class="s0"># 加载新章节</span>
            <span class="s1">self.game_core.load_game_data(next_chapter)</span>
            <span class="s1">self.game_core.preload_resources()</span>

            <span class="s0"># 重置状态并更新显示</span>
            <span class="s1">self.game_core.current_chapter = </span><span class="s4">0</span>
            <span class="s1">self.current_dialogue_idx = </span><span class="s4">0</span>
            <span class="s1">self.update_chapter_display()</span>

        <span class="s2">except </span><span class="s1">Exception </span><span class="s2">as </span><span class="s1">e:</span>
            <span class="s1">messagebox.showerror(</span><span class="s3">&quot;错误&quot;</span><span class="s2">, </span><span class="s3">f&quot;加载章节失败: </span><span class="s2">{</span><span class="s1">str(e)</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>
            <span class="s0"># 回退到上一个有效状态</span>
            <span class="s1">self.game_core.load_game_data()  </span><span class="s0"># 重新加载当前章节</span>
            <span class="s1">self.update_chapter_display()</span>

    <span class="s2">def </span><span class="s1">show_ending(self):</span>
        <span class="s5">&quot;&quot;&quot;显示结局&quot;&quot;&quot;</span>
        <span class="s2">if </span><span class="s1">self._window_destroyed:</span>
            <span class="s2">return</span>

        <span class="s1">self.bg_canvas.delete(</span><span class="s3">&quot;all&quot;</span><span class="s1">)</span>
        <span class="s1">self.bg_canvas.create_text(self.screen_width / </span><span class="s4">2</span><span class="s2">, </span><span class="s1">self.screen_height / </span><span class="s4">2</span><span class="s2">,</span>
                                   <span class="s1">text=</span><span class="s3">&quot;游戏结束，感谢游玩！&quot;</span><span class="s2">,</span>
                                   <span class="s1">font=(</span><span class="s3">&quot;微软雅黑&quot;</span><span class="s2">, </span><span class="s4">48</span><span class="s1">)</span><span class="s2">, </span><span class="s1">fill=</span><span class="s3">&quot;white&quot;</span><span class="s1">)</span>
        <span class="s1">self.game_core.play_bgm(</span><span class="s2">None</span><span class="s1">)  </span><span class="s0"># 停止背景音乐</span>


<span class="s2">if </span><span class="s1">__name__ == </span><span class="s3">&quot;__main__&quot;</span><span class="s1">:</span>
    <span class="s1">root = tk.Tk()</span>
    <span class="s2">try</span><span class="s1">:</span>
        <span class="s2">from </span><span class="s1">ctypes </span><span class="s2">import </span><span class="s1">windll</span>

        <span class="s1">windll.shcore.SetProcessDpiAwareness(</span><span class="s4">1</span><span class="s1">)</span>
    <span class="s2">except</span><span class="s1">:</span>
        <span class="s2">pass</span>

    <span class="s1">resource_paths = {</span>
        <span class="s3">&quot;images&quot;</span><span class="s1">: </span><span class="s3">&quot;images&quot;</span><span class="s2">,</span>
        <span class="s3">&quot;sounds&quot;</span><span class="s1">: </span><span class="s3">&quot;sounds&quot;</span><span class="s2">,</span>
        <span class="s3">&quot;dialogues&quot;</span><span class="s1">: </span><span class="s3">&quot;dialogues&quot;</span>
    <span class="s1">}</span>

    <span class="s0"># 使用try-except捕获初始化过程中的任何异常</span>
    <span class="s2">try</span><span class="s1">:</span>
        <span class="s1">app = VisualNovelUI(root</span><span class="s2">, </span><span class="s1">resource_paths)</span>
        <span class="s1">root.mainloop()</span>
    <span class="s2">except </span><span class="s1">Exception </span><span class="s2">as </span><span class="s1">e:</span>
        <span class="s1">messagebox.showerror(</span><span class="s3">&quot;致命错误&quot;</span><span class="s2">, </span><span class="s3">f&quot;游戏无法启动:</span><span class="s2">\n{</span><span class="s1">str(e)</span><span class="s2">}</span><span class="s3">&quot;</span><span class="s1">)</span>
        <span class="s1">root.destroy()</span></pre>
</body>
</html>